import { matches, get, isEqual, uniqWith } from 'lodash'

import { BatchEvent } from '@slardar/sdk-template'

import { createChainInstance } from './utils'

/** 确保多实例场景下，所有事件都被正确生产且内容完全相等
 *  @param options.idList 多实例 id 数组
 *  @param options.idField id 字段名，web 为 bid, apm plus 为 aid
 *  @param options.matchObj 期望匹配的对象
 *  @param options.matchCount 期望匹配对象出现的次数，小于或大于都将失败，为-1时不做校验
 *  @param options.timeout 等待每个请求的最大窗口期
 *  @param options.maxTryCount 最多需判断的请求数
 *  @param options.equalProps 期望每个匹配到的对象的哪些属性内容需要完全相等
 *  @param options.onSuccess 匹配成功后的回调
 */
export function waitMultiBatchReqAndAssert(options: {
  idList: (string | number)[]
  idField: string
  matchObj: any
  matchCount?: number
  timeout?: number
  maxTryCount?: number
  equalProps?: string[]
  onSuccess?: (matchedObjs: Record<string, any[]>) => void
}) {
  const defaultOptions = {
    matchCount: -1,
    timeout: 2000,
    maxTryCount: Infinity,
    equalProps: [] as string[],
  }
  const { idList, idField, matchObj, matchCount, timeout, maxTryCount, equalProps, onSuccess } = {
    ...defaultOptions,
    ...options,
  }
  // 收集已经匹配到的对象，键为 id，值为收集到的事件数组
  const matchedObjs: Record<string, any[]> = idList.reduce(
    (res, id) => ({
      ...res,
      [id]: [],
    }),
    {},
  )
  function handleSuccess() {
    // 校验属性全等
    if (equalProps.length > 0) {
      // 先把要比较的对象存到一个数组里
      const objList: any[] = []
      for (const matchedObjList of Object.values(matchedObjs)) {
        matchedObjList.forEach((obj) => {
          const pickedObj = {}
          equalProps.forEach((prop) => {
            pickedObj[prop] = get(obj, prop)
          })
          objList.push(pickedObj)
        })
      }
      // 深对比去重
      const uniqObjList = uniqWith(objList, isEqual)
      // 只有去重后只剩下一条数据才符合预期
      expect(uniqObjList.length, `expect all props picked is equal`).equal(1)
    }
    expect(true).true
    onSuccess?.(matchedObjs)
  }
  createChainInstance<BatchEvent<any>>({
    getChainInstance: () => cy.wait('@post', { timeout }).its('request.body'),
    maxTryCount,
    handler: (body) => {
      if (body.list) {
        // body 中取出对应的 id
        const id = body.list[0].common[idField]
        if (matchedObjs[id]) {
          // 存储匹配 matchObj 的数据存储到对应 id 的数组中
          matchedObjs[id].push(...body.list.filter(matches(matchObj)))
          // 校验匹配数量，如果严格限制了 matchCount，那么超出数量的可以提前断言报错
          if (matchCount !== -1 && matchedObjs[id].length > matchCount) {
            expect(false, `expect ${id} match objs count is ${matchCount}`).true
          }
          // 当 maxTryCount 为无穷大时，那么终止条件为要么是所有收集的对象数量都匹配，要么是某次等待的请求超时。
          if (maxTryCount === Infinity && validCount(matchCount, matchedObjs)) {
            return true
          }
        }
      }
      return false
    },
    onMaxCount: () => {
      if (validCount(matchCount, matchedObjs)) {
        handleSuccess()
      } else {
        expect(false, `expect all id match objs count is ${matchCount}`).true
      }
    },
    onSuccess: handleSuccess,
  })
}

function validCount(matchCount: number, matchedObjs: Record<string, any[]>) {
  if (matchCount !== -1) {
    return Object.values(matchedObjs).every((objs) => objs.length === matchCount)
  }
  return true
}
